
Le marché immobilier est très changeant selon le contexte économique et politique. Ainsi, l'accès à des données fiables et à jour est devenu essentiel, c'est pourquoi le gouvernement français à mis à disposition le jeu de données 'Demandes de valeurs foncières' qui permettent de connaître les transactions immobilières intervenues au cours des cinq dernières années sur le territoire métropolitain et les DOM-TOM.
Ainsi, notre projet a pour but d'explorer et d'analyser le jeu de données 'Demandes de valeurs foncières' pour comprendre des tendances, analyser des phénomènes et en tirer des conclusions pertinentes, le tout en utilisant le langage Python.
Le jeu de données Demandes de valeurs foncières contient de multiples précisions sur les transactions immobilières (valeur de la transaction, adresse du bien, caractéristiques du bien...). Ces données sont plus ou moins complètes, ce qui va nous amener à les nettoyer pour qu'elles soient pleinement utilisables.
Nous allons utiliser le langage Python pour réaliser ce projet car il est très efficace lorsqu’il faut traiter un grand nombre de données. Pour lire les données nous allons utiliser Pandas et pour la visualisation nous allons utiliser matplotlib, seaborn ou encore folium. Enfin, nous ferons une vitrine d'analyse et de visualisation avec Django permettant à l’utilisateur d’interagir avec les données.
Nous nous sommes réparti le travail en 3 parts égales.
Dans cette partie nous allons charger les données que nous avons obtenu sur le site du gouvernement puis les nottoyer pour qu'elles soient exploitables. Par la suite, nous allons les explorer rapidements pour découvrir les caractéristiques des datasets (nom des colonnes, nombre de lignes, etc).
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('full.csv', sep=(','))
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1411711681.py:4: DtypeWarning: Columns (10,12,14,16,17,18,20,22,35,36) have mixed types. Specify dtype option on import or set low_memory=False.
df = pd.read_csv('full.csv', sep=(','))
Nous remarquons plusieurs colonnes intéressantes que nous allons pouvoir exploiter (Code département, valeur foncière, surface,...). On remarque également que la latitude et la longitude nous permettrons sans doute d'afficher une carte.
df.shape
(4617590, 40)
Nous remarquons qu'il y a plus de 4 millions de lignes, représentant une grande quantité d'informations. Ces lignes ne sont sans doute pas toutes utiles.
Le nettoyage de données est important lorsque l'on souhaite analyser des données. Il sert à traiter les données brutes pour les rendre utilisables et représentatives (Détection des valeurs abérrantes, Suppression des valeurs manquantes..).
On a remarqué que certaines colonnes étaient constituées uniquement de NaN on décide donc de les supprimer. On effectue cela car elles ne sont pas exploitables dans notre projet :
df = df.dropna(axis=1, how='all')
Nous avons également observé la présence de lignes identiques, que nous avons supprimé :
df = df.drop_duplicates()
Grâce à cette opération, nous avons supprimé le 345 000 lignes, ce qui va nous servir à avoir des données proches de la réalité.
df.shape
(4273660, 40)
Nous avons également remarqué que les codes postaux/ numéro d'adresse / nombre de pièces principales étaient enregistrés en float, ce qui n'était pas logique, ce sont des nombres entiers. Nous les avons donc converti en int.
df['code_postal'] = df['code_postal'].fillna(0)
df['code_postal'] = df['code_postal'].astype(int)
df['adresse_numero'] = df['adresse_numero'].fillna(0)
df['adresse_numero'] = df['adresse_numero'].astype(int)
df['code_postal']
0 1000
1 1480
2 1480
3 1480
4 1480
...
4617585 75016
4617586 75016
4617587 75016
4617588 75006
4617589 0
Name: code_postal, Length: 4273660, dtype: int64
Nous avons remarqué que les appartements d'un immeuble ont chacun comme valeur le prix de l'immeuble (ce qui fausse tous les calculs). Nous allons donc trier sur les id parcelle pour en avoir que des différents :
print('Nombre de parcelle en double :', len(df['id_parcelle']) - len(df['id_parcelle'].unique()))
Nombre de parcelle en double : 1875019
df.drop_duplicates(subset='id_parcelle', keep=False, inplace=True)
Maintenant que nous avons nettoyé notre DataFrame, nous allons visualiser les données pour les analyser. Nous allons d'abord voir les différents types de mutations ayant eu lieux en 2022.
import seaborn as sns
plt.title('Type de mutation',fontsize='12')
sns.countplot(y='nature_mutation', data=df)
<Axes: title={'center': 'Type de mutation'}, xlabel='count', ylabel='nature_mutation'>
On remarque une dominance des ventes sur l'année 2022 ce qui semble normal. D'autre part nous allons nous intéresser à la valeur de ces ventes. Nous allons donc afficher le prix au m² (entre la surface réelle bati & la valeur foncière).
plt.scatter(df['surface_reelle_bati'], df['valeur_fonciere'], alpha=0.5, color='green')
plt.xlabel('surface réelle bati (m²)')
plt.ylabel('Valeur foncière (€)')
plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)
plt.show()
Nous remarquons plusieurs choses :
Il y a des valeurs aberrantes dans les surfaces
Il y a des valeurs aberrantes dans les valeurs foncières
Sinon les valeurs sont situées à la base de notre graphique.
Pour comprendre d'où viennent ces valeurs aberrantes, regardons la moyenne des valeurs foncières par départements.
df['valeur_fonciere'] = df['valeur_fonciere'].astype(float)
moyenne_par_departement = df.groupby('code_departement')['valeur_fonciere'].mean()
print(moyenne_par_departement)
code_departement
1 252915.298254
2 112024.011686
3 112626.093707
4 203568.554434
5 149792.605358
...
974 281413.748196
29 167222.506366
2A 409119.627986
2B 218762.600368
30 225527.662074
Name: valeur_fonciere, Length: 99, dtype: float64
Maintenant que nous avons les valeurs foncières moyenne par département, nous allons regarder le département avec la valeur foncière moyenne la plus élevée (probablement celui qui contiendra les valeurs aberrantes) :
print('La valeur foncière moyenne la plus élevée est de', moyenne_par_departement.max(), 'dans le', moyenne_par_departement.idxmax())
La valeur foncière moyenne la plus élevée est de 7425867.287267152 dans le 56
Nous remarquons que cette valeur est anormalement évelée par rapport aux autres, ce qui peut-être lié à des valeurs aberrantes. Pour s'en assurer, on va tracer la relation entre la surface réelle bâtie et la valeur foncière dans le 56 :
df_56 = df[df['code_departement'] == 56]
plt.scatter(df_56['surface_reelle_bati'], df_56['valeur_fonciere'], alpha=0.5, color='green')
plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)
plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')
plt.show()
Effectivement, il il a des valeurs aberrantes qui sont beaucoup plus élevées que la plupart des autres, ce qui fausse notre valeur foncière moyenne dans ce département. Pour trouver la cause de ces valeurs abérrantes allons afficher la valeur foncière moyenne par rapport à la surface réelle bati en fonction du type local :
df_56_appart = df[(df['type_local'] == 'Appartement') & (df['code_departement'] == 56)]
df_56_maison = df[(df['type_local'] == 'Maison') & (df['code_departement'] == 56)]
df_56_dependance = df[(df['type_local'] == 'Dépendance') & (df['code_departement'] == 56)]
df_56_local_industriel = df[(df['type_local'] == 'Local industriel. commercial ou assimilé') & (df['code_departement'] == 56)]
plt.scatter(df_56_appart['surface_reelle_bati'], df_56_appart['valeur_fonciere'], alpha=0.5, color='blue', label='Appartement')
plt.scatter(df_56_maison['surface_reelle_bati'], df_56_maison['valeur_fonciere'], alpha=0.5, color='orange', label='Maison')
plt.scatter(df_56_dependance['surface_reelle_bati'], df_56_dependance['valeur_fonciere'], alpha=0.5, color='red', label='Dépendance')
plt.scatter(df_56_local_industriel['surface_reelle_bati'], df_56_local_industriel['valeur_fonciere'], alpha=0.5, color='green', label='Local industriel/commercial')
plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')
plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)
plt.legend()
plt.show()
Nous voyons que les valeurs aberrantes sont liées aux types Dépendance et Local industriel/commercial. Affichons maintenant ce graphique sans les types Dépendance et Local industriel/commercial :
plt.scatter(df_56_appart['surface_reelle_bati'], df_56_appart['valeur_fonciere'], alpha=0.5, color='blue', label='Appartement')
plt.scatter(df_56_maison['surface_reelle_bati'], df_56_maison['valeur_fonciere'], alpha=0.5, color='orange', label='Maison')
plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')
plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)
plt.legend()
plt.show()
Sans les types Dépendance et Local industriel/commercial il n'y a presque pas de valeur aberrantes, nous pouvons donc en conclure que ces valeurs étaient liés à de gros locaux industriels.
Nous allons maintenant utiliser une autre surface pour calculer le prix au m² surface Carrez : basé sur la surface de plancher avec au moins 1,80 m de plafond. Cette surface est couramment utilisée pour calculer le prix au m². Enfin , nous allons pouvoir comparer ces 2 rapports.
Nous voyons qu'il y a plusieurs surfaces Carrez :
Surface Carrez du 1er lot,Surface Carrez du 2eme lot, Surface Carrez du 3eme lot, Surface Carrez du 4eme lot,Surface Carrez du 5eme lotNous allons les explorer pour voir laquelle est la plus utile.
s_carrez = ['lot1_surface_carrez','lot2_surface_carrez','lot3_surface_carrez','lot4_surface_carrez','lot5_surface_carrez']
for i in s_carrez:
nb_nan = df[i].isna().sum()
nb_lignes = len(df)
print(f'Nombre de NaN dans la colonne '{i}': {(nb_nan*100)/nb_lignes} %')
Nombre de NaN dans la colonne 'lot1_surface_carrez': 98.46355267116614 % Nombre de NaN dans la colonne 'lot2_surface_carrez': 99.85138860372967 % Nombre de NaN dans la colonne 'lot3_surface_carrez': 99.96360072957788 % Nombre de NaN dans la colonne 'lot4_surface_carrez': 99.98743087693236 % Nombre de NaN dans la colonne 'lot5_surface_carrez': 99.99317513679586 %
On remarque que les colonnes sont quasiment toutes composées de NaN sauf la colonne lot1_surface_carrez. Nous allons donc utiliser cette colonne pour calculer le prix du mettre carré.
df['prix_m_2'] = df.valeur_fonciere / df.lot1_surface_carrez
print(df[df['prix_m_2'].notna()]['prix_m_2'])
216 557.142857
374 1313.105558
569 4232.199028
589 3910.226747
743 2630.850180
...
4617464 10206.185567
4617479 10470.956720
4617491 10000.000000
4617500 9476.489967
4617540 15260.323160
Name: prix_m_2, Length: 27009, dtype: float64
Nous allons maintenant trier ces prix/m² par département.
prix_m_2_dep = df.groupby('code_departement')['prix_m_2'].mean()
print(prix_m_2_dep)
code_departement
1 2654.297720
2 1620.022921
3 3312.924064
4 1831.878801
5 2633.495242
...
974 2716.516912
29 2192.442693
2A 4897.430483
2B 3454.565274
30 2221.763134
Name: prix_m_2, Length: 99, dtype: float64
On peut aussi afficher le rapport valeur foncière sur loi Carrez
plt.scatter(df['lot1_surface_carrez'], df['valeur_fonciere'], alpha=0.5, color='green')
plt.xlabel('Surface loi Carrez (m²)')
plt.ylabel('Valeur foncière (€)')
plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)
plt.show()
Nous remarquons qu'il y a beaucoup moins de valeur aberrante que dans nos précédentes visualisations. Ainsi ce rapport est beaucoup plus pertinent pour évaluer le prix au m².
Je peux également afficher la répartition du prix au mètre carré dans toute la France :
import plotly.express as px
prix_m_2_dep_sorted_all = prix_m_2_dep.sort_values(ascending=False)
fig = px.pie(names=prix_m_2_dep_sorted_all.index, values=prix_m_2_dep_sorted_all.values, title='Prix au mètre carré en France')
fig.update_traces(textposition='inside', textinfo='percent+label', insidetextorientation='radial', textfont_size=10)
fig.show()
Avec ces données, nous avons comme idée d'aller chercher une base de données contenant le nombre d'habitant par département en 2022. Pour cela nous allons scraper ces données à partir d'un site web :
import requests
from bs4 import BeautifulSoup
import csv
url = 'https://ville-data.com/nombre-d-habitants/nombre-d-habitants-par-departement'
response = requests.get(url)
scrap = BeautifulSoup(response.text, 'html.parser')
with open('population_data.csv', 'w', newline='') as csvfile:
csv = csv.writer(csvfile, delimiter='\t')
csv.writerow(['code', 'nom', 'hab'])
for row in scrap.select('table tr')[1:]:
columns = row.find_all('td')
code = columns[0].text.strip()
nom = columns[1].text.strip()
pop = columns[2].text.strip()
csv.writerow([code, nom, pop])
df_popu = pd.read_csv('population_data.csv', sep=('\t'), encoding='UTF-8')
df_popu = df_popu.sort_values('code', ascending= True)
print('Le', df_popu.loc[df_popu['hab'].idxmax(), 'code'], 'est le plus peuplé avec', df_popu.hab.max(), 'habitants')
Le 59 est le plus peuplé avec 2592185 habitants
print('Le', df_popu.loc[df_popu['hab'].idxmin(), 'code'], 'est le moins peuplé avec', df_popu.hab.min(), 'habitants')
Le 48 est le moins peuplé avec 75700 habitants
Avec ces données, nous souhaitons afficher la surface de terrain par habitant :
On fait d'abord la somme des surfaces de terrain du 59 et du 48 :
sum_surface_59 = df[df['code_departement'] == 59]['surface_terrain'].sum()
print(sum_surface_59)
sum_surface_48 = df[df['code_departement'] == 48]['surface_terrain'].sum()
print(sum_surface_48)
62631870.0 28885244.0
On le divise par le nombre d'habitants :
sum_surface_59_hab = sum_surface_59 / df_popu.hab.max()
print(sum_surface_59_hab)
sum_surface_48_hab = sum_surface_48 / df_popu.hab.min()
print(sum_surface_48_hab)
24.161805581005986 381.5752179656539
plt.bar(['59', '48'], [sum_surface_59_hab, sum_surface_48_hab], color=['blue', 'green'])
plt.xlabel('Département')
plt.ylabel('Surface de terrain par habitants')
plt.show()
Nous voyons que le rapport surface de terrain par habitants est beaucoup plus élevé dans le 48, car il y a beaucoup moins d'habitants.
Nous pouvons afficher le nombre d'habitants dans un graphique :
import plotly.express as px
fig = px.pie(df_popu, values='hab', names='nom', title='Répartition de la population par département')
fig.update_traces(textposition='inside', textinfo='percent+label', insidetextorientation='radial', textfont_size=10)
fig.show()
Nous pouvons voir la densité de population sur la carte de France et à partir des données scrappées :
import plotly.express as px
import geopandas as gpd
gj = gpd.read_file('departements-version-simplifiee.geojson')
df_map = pd.merge(gj, df_popu, on='code')
fig = px.choropleth_mapbox(df_popu, geojson=gj, color='hab',
locations='code',
center = {'lat': 46.23, 'lon': 2.2},
featureidkey='properties.code',
mapbox_style='carto-positron', zoom = 4)
fig.show()
Nous pouvons afficher la valeur foncière moyenne possédée par personne dans le 59 par rapport au 48 :
prix_59 = prix_m_2_dep.loc[59]
print(prix_59)
3982.224580474231
prix_59_pers = df_popu.hab.max() / prix_59
prix_59_pers
650.9389281333059
prix_48 = prix_m_2_dep.loc[48]
print(prix_48)
1103.1757626739773
prix_48_pers = df_popu.hab.min() / prix_48
prix_48_pers
68.62007176128624
plt.bar(['59', '48'], [prix_59_pers, prix_48_pers], color=['blue', 'green'])
plt.xlabel('Département')
plt.ylabel('Valeur foncière moyenne')
plt.show()
Nous remarquons que dans le 59 et pour un habitant, les valeurs foncières sont en moyenne 7 fois plus élevée que dans le 48.
Nous pouvons afficher les 3 départements les plus peuplés en France :
df_popu_sorted = df_popu.sort_values(by='hab', ascending=False)
top3_departements = df_popu_sorted.head(3)
fig = px.bar(top3_departements, x='code', y='hab', labels={'code': 'Code département', 'hab': 'Nombre d\'habitants'},
title='Les 3 départements les plus peuplés en France')
fig.show()
Nous pouvons les comparer avec les 3 départements les plus chèrs au mètre carré :
prix_m_2_dep_sorted = prix_m_2_dep.sort_values(ascending=False).head(3)
print(prix_m_2_dep_sorted)
code_departement 75 13014.102997 72 12348.279410 61 9068.274111 Name: prix_m_2, dtype: float64
fig = px.bar(prix_m_2_dep_sorted, x=prix_m_2_dep_sorted.index, y='prix_m_2',
labels={'prix_m_2': 'Prix par mètre carré'},
title='Les 3 départements les plus chèrs au mètre carré')
fig.update_layout(xaxis_title='Département', yaxis_title='Prix au mètre carré')
fig.update_xaxes(type='category')
fig.show()
On observe que le 75 figure dans les deux classements, ce qui signifi qu'il est à la fois parmis les plus peuplés en France mais également les plus chers. À noter que la préfectures (Paris) est la plus grande ville de France. Nous voyons donc que le département le plus chère de France est aussi un des plus peuplé (ces deux phénomènes sont liés).
Nous pouvons observer le type de local le plus fréquent lors des ventes en 2022 :
sns.countplot(y='type_local', data=df)
<Axes: xlabel='count', ylabel='type_local'>
Nous pouvons également comparer 2 départements en terme de type de local : Prenons le 78 (Yvelines) et le 35 (Ille-et-Vilaine).
df_75 = df[df['code_departement'] == 74]
df_35 = df[df['code_departement'] == 35]
df_75['Département'] = 75
df_35['Département'] = 35
combined_df = pd.concat([df_75, df_35])
sns.countplot(y='type_local', hue='Département', data=combined_df)
plt.show()
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1283902776.py:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy /var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1283902776.py:4: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
On voit que le nombre de ventes de maison dans le 35 est largement plus important que dans le 75 tous simplement car il y a beaucoup plus de maison dans le 35.
Nous pouvons observer l'évolution du prix minimum d'une transaction :
df_ev = df.copy()
df_ev['date_mutation'] = pd.to_datetime(df_ev['date_mutation'])
df_ev = df_ev.set_index('date_mutation').sort_index()
df_Visu = df_ev[['valeur_fonciere']]
df_Visu.resample('M').min().plot()
<Axes: xlabel='date_mutation'>
Évolution du prix maximum d'une transaction :
df_ev = df
df_ev['date_mutation'] = pd.to_datetime(df_ev['date_mutation'])
df_ev = df_ev.set_index('date_mutation').sort_index()
df_Visu = df_ev[['valeur_fonciere']]
df_Visu.resample('M').max().plot()
<Axes: xlabel='date_mutation'>
Évolution du prix moyen d'une transaction :
df_ev = df
df_ev['date_mutation'] = pd.to_datetime(df_ev['date_mutation'])
df_ev = df_ev.set_index('date_mutation').sort_index()
df_Visu = df_ev[['valeur_fonciere']]
df_Visu.resample('M').mean().plot()
<Axes: xlabel='date_mutation'>
On voit un pic au mois de décembre ce qui est souvent le cas en fin d'années (ficalement intéressant).
Nous allons voir une cartographie des expropriations en 2022 :
df_expro =df[df['latitude'].notna() & (df['longitude'].notna()) & (df['nature_mutation'] == 'Expropriation')]
import folium
carte = folium.Map(location=[43.327408, -1.032999], zoom_start=5)
for ind, lat, lon, place in df_expro[['latitude', 'longitude', 'nom_commune']].itertuples():
carte.add_child(folium.RegularPolygonMarker(location=[lat,lon], popup=place,fill_color='red', radius=10))
carte
Comme nous avons vu plus tôt, il y a des valeurs aberrantes dans le 56, nous allons le vérifier sur la carte de France :
df_popu['code'] = df_popu['code'].astype(str).str.zfill(2)
dep_manquant3 = ['57', '67', '68','971', '972', '973', '974', '976']
df_popu = df_popu[~df_popu['code'].isin(dep_manquant3)]
df_popu = df_popu.sort_values(by='code')
dep_manquant = ['57', '67', '68']
gj = gj[~gj['code'].isin(dep_manquant)]
dep_manquant2 = [971, 972, 973, 974]
df = df[~df['code_departement'].isin(dep_manquant2)]
df['code_departement'] = df['code_departement'].astype(str).str.zfill(2)
gj['code'] = gj['code'].astype(str).str.zfill(2)
gj = gj.sort_values(by='code')
df_map = pd.merge(gj, df_popu, on='code')
dep_manquant4 = ['57', '67', '68',971, 972, 973, 974, 976]
moyenne_par_departement = moyenne_par_departement[~moyenne_par_departement.index.isin(dep_manquant4)]
moyenne_par_departement.index = moyenne_par_departement.index.astype(str).str.zfill(2)
moyenne_par_departement_update = moyenne_par_departement[~moyenne_par_departement.index.duplicated(keep='first')]
df_moyenne = pd.DataFrame(moyenne_par_departement_update)
df_moyenne['code'] = df_moyenne.index
df_moyenne
df_map = pd.merge(df_map, df_moyenne[['code', 'valeur_fonciere']],
left_on='code',
right_on='code',
how='inner')
df_map
fig = px.choropleth_mapbox(df_map, geojson=gj, color='valeur_fonciere',
locations='code',
center = {'lat': 46.23, 'lon': 2.2},
featureidkey='properties.code',
mapbox_style='carto-positron', zoom = 4)
fig.show()
Les valeurs aberrantes dans le 56 se retrouvent bien sur notre carte de France. Comme vu précédemment, c'est lié aux locaux industriels.
df2 = pd.read_csv('full_2020.csv', sep=(','))
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/862735889.py:1: DtypeWarning: Columns (10,12,14,16,17,18,20,22,35,36) have mixed types. Specify dtype option on import or set low_memory=False.
df2.shape
(3518587, 40)
df2 = df2.dropna(axis=1, how='all')
df2 = df2.drop_duplicates()
df2.drop_duplicates(subset='id_parcelle', keep=False, inplace=True)
Reprenons maintenant notre exemple de comparaison entre le 75 et le 35 en terme de type de bien vendus :
df_75 = df[df['code_departement'] == '75']
df_35 = df[df['code_departement'] == '35']
df_75['Département'] = '75'
df_35['Département'] = '35'
combined_df = pd.concat([df_75, df_35])
sns.countplot(y='type_local', hue='Département', data=combined_df)
plt.title('Comparaison de type de bien vendu entre le 75 et le 35 en 2022')
plt.show()
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1960003802.py:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy /var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1960003802.py:4: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df2_75 = df2[df2['code_departement'] == 75]
df2_35 = df2[df2['code_departement'] == 35]
df2_75['Département'] = 75
df2_35['Département'] = 35
combined_df2 = pd.concat([df2_75, df2_35])
sns.countplot(y='type_local', hue='Département', data=combined_df2)
plt.title('Comparaison de type de bien vendu entre le 75 et le 35 en 2020')
plt.show()
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1698341241.py:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy /var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1698341241.py:4: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
Nous voyons qu'il y a beaucoup plus de vente d'appartement en 2020 dans le 75 par rapport à 2022. Ceci est lié au COVID et aux confinements qui ont rendu la vie difficile dans une appartement.
Nous allons maintenant regarder la différence entre 2022 et 2020 pour le rapport valeur foncière/surface réelle bati dans le 75 :
df_75 = df[df['code_departement'] == '75']
plt.scatter(df_75['surface_reelle_bati'], df_75['valeur_fonciere'], alpha=0.5, color='green')
plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)
plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')
plt.title('Prix mètre carré dans le 75 en 2022')
plt.show()
df_75 = df2[df2['code_departement'] == 75]
plt.scatter(df_75['surface_reelle_bati'], df_75['valeur_fonciere'], alpha=0.5, color='green')
plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)
plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')
plt.title('Prix mètre carré dans le 75 en 2020')
plt.show()
Nous voyons que les valeurs sont beaucoup plus éparpillées en 2020 (valeurs aberrantes).
Voyons maintenant le prix moyen d'une transaction en 2022 :
df_ev = df
df_ev['date_mutation'] = pd.to_datetime(df_ev['date_mutation'])
df_ev = df_ev.set_index('date_mutation').sort_index()
df_Visu = df_ev[['valeur_fonciere']]
df_Visu.resample('M').mean().plot()
<Axes: xlabel='date_mutation'>
En comparaison avec le prix moyen d'une transaction en 2020 :
df_ev2 = df2
df_ev2['date_mutation'] = pd.to_datetime(df_ev2['date_mutation'])
df_ev2 = df_ev2.set_index('date_mutation').sort_index()
df_Visu2 = df_ev2[['valeur_fonciere']]
df_Visu2.resample('M').mean().plot()
<Axes: xlabel='date_mutation'>
Il est clair que l'année 2020 a été beaucoup plus changeante que l'années 2022. Nous voyons même que l'entrée dans le confinement en mars 2020 a provoqué une montée des prix.
Nous pouvons également comparer les expropriations de 2022 :
carte = folium.Map(location=[43.327408, -1.032999], zoom_start=5)
for ind, lat, lon, place in df_expro[['latitude', 'longitude', 'nom_commune']].itertuples():
carte.add_child(folium.RegularPolygonMarker(location=[lat,lon], popup=place,fill_color='red', radius=10))
carte
Avec les expropriations de 2020 :
df_expro2 =df2[df2['latitude'].notna() & (df2['longitude'].notna()) & (df2['nature_mutation'] == 'Expropriation')]
carte = folium.Map(location=[43.327408, -1.032999], zoom_start=5)
for ind, lat, lon, place in df_expro2[['latitude', 'longitude', 'nom_commune']].itertuples():
carte.add_child(folium.RegularPolygonMarker(location=[lat,lon], popup=place,fill_color='red', radius=10))
carte
Nous voyons qu'il y a beaucoup plus d'expropriations en 2020 qu'en 2022, principalement dans les grandes villes.
Enfin, nous pouvons observer la différence de type de local dans les demandes de valeurs foncières entre 2022 et 2020 :
sns.countplot(y='type_local', data=df)
<Axes: xlabel='count', ylabel='type_local'>
sns.countplot(y='type_local', data=df2)
<Axes: xlabel='count', ylabel='type_local'>
On remarque qu'il y a beaucoup plus de vente de maison en 2020 qu'en 2022 (33% de plus)
Notre vidéo sur Django est disponible dans le dossier du projet.